package net.zoneland.knowledge.controller;


import com.alibaba.excel.util.BooleanUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import net.zoneland.knowledge.constant.Constants;
import net.zoneland.knowledge.model.*;
import net.zoneland.knowledge.service.AttachmentService;
import net.zoneland.knowledge.service.PreviewAppKeyService;
import net.zoneland.knowledge.service.PreviewService;
import net.zoneland.knowledge.service.QaBankService;
import net.zoneland.knowledge.utils.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

import static net.zoneland.knowledge.utils.GsonUtils.GSON;

/**
 * 附件预览.
 */
@RestController
@RequestMapping("/preview")
public class AttachmentPreviewController {

    private static final Logger LOGGER = LoggerFactory.getLogger(AttachmentPreviewController.class);

    private static final ZoneId zoneId = ZoneId.systemDefault();

    @Autowired
    private QaBankService qaBankService;

    @Autowired
    private PreviewService previewService;

    @Autowired
    private PreviewAppKeyService appKeyService;

    @Autowired
    private AttachmentService attachmentService;

    /**
     * json工具类.
     */
    @Autowired
    private ObjectMapper objectMapper;

    @Value("${attachmentPreview.oadocs.url}")
    private String oadocsUrl;

    /**
     * 应用在服务中的注册ID.
     */
    @Value("${attachmentPreview.headers.appId}")
    private String oadocsAppId;

    /**
     * 获取appkey地址.
     */
    @Value("${attachmentPreview.headers.appKeyUrl}")
    private String appKeyUrl;

    /**
     * 获取appkey地址.
     */
    @Value("${attachmentPreview.headers.userId}")
    private String oaUserId;

    /**
     * 地址.
     */
    @Value("${talk.siteUrl}")
    private String siteUrl;

    /**
     * 失败标志.
     */
    private static final String ZERO = "0";

    /**
     * 页面加载答案出来，附件生成新的id，拿到附件信息， 生成下载地址（注意下载接口要写在不用认证的地方），存到新的表里面 并返回前端放到form里面   .
     *
     * @param request  http请求
     * @param response http响应
     */
    @RequestMapping(value = "/previewInfo", method = RequestMethod.GET)
    public String previewInfo(final HttpServletRequest request, final String fileName,
                                              final String id, final String fileId, final String type, final boolean isQabank, final HttpServletResponse response) {
        final ResponseResult result = new ResponseResult();
        try {
            Preconditions.checkNotNull(id, "查询参数不能为空！");
            final LoginUserPojo user = LoginUtils.getCurrentUserByToken(request.getHeader(Constants.TOKEN_NAME));
            final AttachmentPreviewForm form = isQabank ? buildQAObj(id,type,fileId) : buildWorkApplyObj(id,type);
            if(Objects.nonNull(user) && Objects.nonNull(form)){
                final AttachmentPreviewReturn previewReturn = post(form, user.getUserID());
                previewReturn.setSuccess(Boolean.TRUE);
                result.setData(previewReturn);
            }
        } catch (final Exception e) {
            result.setResult(ZERO);
            result.setMessage("预览附件信息异常！fileid:"+fileId);
            LOGGER.error("预览附件信息异常,fileName:{},fileId:{}, id:{}", fileName, fileId, id, e);
        }
        return GsonUtils.toJson(result);
    }

    /**
     * 构建题库对象附件.
     * @param id
     * @param type
     * @param fileId
     * @return
     */
    private AttachmentPreviewForm buildQAObj(final String id, final String type, final String fileId){
        //queryQaBankList方法返回为list或者null
        return Optional.ofNullable(queryQaBankList(id))
                .map(list->{
                    final QaBankWithBLOBs qaBank = list.get(0);
                    final int size = CollectionUtils.size(queryFile(qaBank.getAnsContentFile(), type, fileId));
                    final String name = queryName(qaBank.getAnsContentName(), type, fileId);
                    return buildObj(id, fileId, name, size, type, Boolean.TRUE);
                }).orElse(null);
    }

    /**
     * 查询qaBank表.
     * @param id
     * @return list<QaBankWithBLOBs>或者null
     */
    private List<QaBankWithBLOBs> queryQaBankList(final String id) {
        final List<QaBankWithBLOBs> qaBankList = qaBankService.queryByPiidAndBankId(null, id);
        return CollectionUtils.isNotEmpty(qaBankList) ? qaBankList : null;
    }

    /**
     * 构建工单附件对象.
     * @param id
     * @param type
     * @return
     */
    private AttachmentPreviewForm buildWorkApplyObj(final String id, final String type){
        return Optional.ofNullable(attachmentService.findWorkAttachmentById(id)).map(info -> buildObj(id,id,info.getFileName(),info.getAttachment().length,type,Boolean.FALSE)).orElse(null);
    }

    /**
     * 发起wps请求获取AttachmentPreviewReturn.
     * @param previewForm
     * @param userId
     * @return
     * @throws IOException
     */
    private AttachmentPreviewReturn post(final AttachmentPreviewForm previewForm, final String userId) throws IOException {
        return Optional.ofNullable(objectMapper.readValue(doPreview(previewForm, userId), AttachmentPreviewReturn.class)).orElse(new AttachmentPreviewReturn());
    }

    /**
     * 构建请求form
     * @param fileId
     * @param name
     * @param size
     * @param type
     * @return
     */
    private AttachmentPreviewForm buildObj(final String id, final String fileId, final String name, final int size, final String type, final boolean isQabank) {
        final AttachmentPreviewForm previewForm = new AttachmentPreviewForm();
        previewForm.setId(OplogUtils.getUuid32());
        previewForm.setFileTableId(id);
        previewForm.setSourceQaBank(isQabank);
        previewForm.setFileId(fileId);
        previewForm.setFileName(name);
        previewForm.setStatus(1);
        previewForm.setSize(size);
        previewForm.setType(type);
        previewService.insert(previewForm);
        return previewForm;
    }

    /**
     * 设置header接口验证参数.
     * @author pbody
     * @date 2:31 PM 2022/7/22
     * @param acquireNewKey 是否失效重新获取appKey
     * @param appKey 应用在服务中的注册KEY
     * @param userId 用户id
     * @return java.util.Map<java.lang.String,java.lang.String>
     **/
    private Map<String, String> getHeaderAuthMap(final Boolean acquireNewKey, final String appKey, final String userId) {

        final Map<String, String> headersMap = new HashMap<>();
        headersMap.put("appId", oadocsAppId);
        if (BooleanUtils.isFalse(acquireNewKey)) {
            final String reqTime = String.valueOf(new Date().getTime());
            headersMap.put("reqTime", reqTime);
            headersMap.put("userId", userId);
            final String token = DigestUtils.md5Hex(reqTime + appKey);
            headersMap.put("token", token);
        } else {
            headersMap.put("lastAppKey", appKey);
        }
        return headersMap;
    }


    /**
     * 重定向到返回的预览链接地址（单击查看附件，拿id去数据库查询数据，有就返回信息，同时disable为false   然后调用在线预览接口，将）.
     * @param form
     * @throws IOException
     */
    private String doPreview( final AttachmentPreviewForm form, final String userId) throws IOException {
        final String formString = JsonUtils.writeValueAsString(trans(form));
        final Map<String, String> headMap = getHeadMapWithUserId(userId);
        LOGGER.info("doPreview form:[{}]", formString);
        LOGGER.info("doPreview headMap:[{}]", headMap);
        return HttpClientUtils.doPost(oadocsUrl, formString, headMap);
    }

    /**
     * 根据userId构建请求头.
     * @param userId
     * @return
     * @throws IOException
     */
    private Map<String, String> getHeadMapWithUserId(final String userId) throws IOException {
        final AttachmentPreviewAppKey appKeyInfo = appKeyService.queryAppKey();
        final boolean isInvalid = isTimeOut(appKeyInfo.getCreateTime());
        final Map<String, String> headMap = getHeaderAuthMap(isInvalid, appKeyInfo.getAppKey(), userId);
        if(isInvalid){//如果appkey失效了
            queryNewAppKey(headMap, appKeyInfo.getId(), userId);
        }
        return headMap;
    }

    /**
     * 查询新的appkey.
     * @param headMap
     * @param appKeyId
     * @param userId
     * @throws IOException
     */
    private void queryNewAppKey(final Map<String, String> headMap, final String appKeyId, String userId) throws IOException {
        final AttachmentPreviewReturn previewReturn = objectMapper.readValue(HttpClientUtils.doGet(appKeyUrl, headMap), AttachmentPreviewReturn.class);
        if(1 == previewReturn.getCode() ) {
            final AttachmentPreviewAppKey previewAppKey = new AttachmentPreviewAppKey();
            previewAppKey.setAppKey(previewReturn.getData().toString());
            previewAppKey.setStatus(1);
            previewAppKey.setId(OplogUtils.getUuid32());
            appKeyService.insert(previewAppKey);
            appKeyService.update(appKeyId);
            getHeadMapWithUserId(userId);
        }
    }

    /**
     * 判断是否超过2个月.
     * @param createTime
     * @return
     */
    private boolean isTimeOut(final Date createTime) {
        return LocalDateTime.ofInstant(createTime.toInstant(), zoneId).isBefore(LocalDateTime.now().minusMonths(2));
    }

    /**
     * 将form转成请求wps参数.
     * @param form
     * @return
     */
    private AttachmentPreviewParam trans(final AttachmentPreviewForm form) {
        final AttachmentPreviewParam previewParam = new AttachmentPreviewParam();
        previewParam.setId(form.getId());
        previewParam.setName(form.getFileName());
        previewParam.setDownloadUrl(siteUrl+"/preview/downloadFile?id="+form.getId());
        previewParam.setSize(form.getSize());
        return previewParam;
    }

    /**
     * 查询附件信息.
     * @param form
     * @return
     */
    private byte[] queryFileContent(final AttachmentPreviewForm form){
        return form.isSourceQaBank() ?
                  queryFileContentById(form.getFileTableId(), form.getType(), form.getFileId()) :
                  queryWAFileContentById(form.getFileTableId());

    }

    /**
     * 根据qa表id和附件类型以及附件id获取附件.
     * @param id
     * @param type
     * @param fileId
     * @return
     */
    private byte[] queryFileContentById(final String id, final String type, final String fileId){
        //queryQaBankList返回list<QaBankWithBLOBs>或者null
        return Optional.ofNullable(queryQaBankList(id))
                .map(qaList-> queryFile(qaList.get(0).getAnsContentFile(),type,fileId))
                .orElse(null);
    }

    /**
     * 获取WorkApplyInfoAttachment表里面的附件.
     * @param id
     * @return
     */
    private byte[] queryWAFileContentById(final String id){
        return Optional.ofNullable(attachmentService.findWorkAttachmentById(id)).map(attachment-> attachment.getAttachment()).orElse(null);
    }

    /**
     * 下载附件接口.
     * @param request
     * @param id
     * @param response
     */
    @RequestMapping(value = "downloadFile", method = RequestMethod.GET)
    public void downloadFile(final HttpServletRequest request, final String id, final HttpServletResponse response) {
        try {
            final AttachmentPreviewForm form = previewService.queryById(id);
            if (Objects.nonNull(form)) {
                final byte[] fileContent= queryFileContent(form);
                if(null != fileContent && fileContent.length>0) {
                    // 非常重要
                    response.reset();
                    final String userAgent = request.getHeader("User-Agent");
                    String name = form.getFileName();
                    // 针对IE或者以IE为内核的浏览器：
                    if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
                        name = java.net.URLEncoder.encode(name, "UTF-8");
                    } else {
                        // 非IE浏览器的处理：
                        name = new String(name.getBytes("UTF-8"), "ISO-8859-1");
                    }
                    if ("file".equals(form.getType())) {
                        response.setContentType("application/x-msdownload");
                        response.setHeader("Content-Disposition", "attachment; filename=" + name);
                    }
                    IOUtils.copy(new ByteArrayInputStream(fileContent), response.getOutputStream());
                }
            } else {
                response.setContentType("text/plain; charset=utf-8");
                response.getWriter().write("文件下载失败，id:"+id);
            }
        } catch (Exception ex) {
            LOGGER.error("下载异常，id：{}", id, ex);
        }
    }

    /**
     * 根据id和类型获取ansContentFileList里面的file.
     * @param ansContentFile
     * @param type
     * @param fileId
     * @return
     */
    private byte[] queryFile(final byte[] ansContentFile, final String type, final String fileId) {
        return getStrFromMap(strToMap(new String(ansContentFile,
                StandardCharsets.ISO_8859_1)),type, fileId).getBytes(StandardCharsets.ISO_8859_1);
    }

    /**
     * 根据type和fileId获取fileName
     * @param ansContentNam
     * @param type
     * @param fileId
     * @return
     */
    private String queryName(final String ansContentNam, final String type, final String fileId) {
        return getStrFromMap(strToMap(ansContentNam), type, fileId);
    }

    /**
     * 根据type和fileId获取（file或者name的）str.
     * @param strMap
     * @param type
     * @param fileId
     * @return
     */
    private String getStrFromMap(final Map<String, Map<String, String>> strMap, final String type, final String fileId) {
        return strMap.get(type).get(fileId);
    }

    /**
     * String转map.
     * @param str
     * @return
     */
    private Map<String, Map<String, String>> strToMap(final String str) {
        return GSON.fromJson(str, Map.class);
    }

}
